package org.barad.architecture.analyzer.archpad.util;

import static org.barad.architecture.analyzer.archpad.util.Properties.LOG4J_PROPERTIES_FILE_NAME;
import static org.barad.architecture.analyzer.archpad.util.Properties.PROPERTIES_FILE_NAME;

import java.io.BufferedInputStream;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;
import org.barad.architecture.analyzer.properties.bind.Propertiez;
import org.barad.architecture.analyzer.archpad.component.ComponentFactory;
import org.barad.architecture.analyzer.archpad.xml.handler.ComponentDescriptor;
import org.barad.architecture.analyzer.archpad.xml.handler.HandlerFactory;
import org.barad.architecture.analyzer.archpad.xml.handler.PropertiesHandler;

/**
 * This class contains some utility methods used by ArchPad.
 * 
 * @author svetoslavganov@gmail.com (Svetoslav R. Ganov)
 *
 */
public class Util {
	private static Logger log = Logger.getLogger(Util.class);
	private static Properties properties = new Properties();
	private static String modelName;
	private static List<ComponentDescriptor> componentProperties;

	/**
	 * Reads properties from user defined XML file.
	 * If it is invalid reads from the default @see FrameworkProperties.
	 * 
	 * @param file The file to be read from
	 */
	public static void loadSystemPropeties() {
		try {
			URL url = Thread.currentThread().getContextClassLoader().getResource(PROPERTIES_FILE_NAME);
			BufferedInputStream bis = new BufferedInputStream(url.openStream());
			properties.loadFromXML(bis);
		} catch (Exception e) {
			log.error("No properties file " + PROPERTIES_FILE_NAME + " loaded. ArchPad will terminate ", e);
			System.exit(-1);
		}
		log.info("Loading of ArchPad properties - OK");
	}
	
	/**
	 * Loads the Log4j properties. If the file is on the class path
	 * an attempt is made to load it from the current folder. 
	 */
	public static void loadLog4jProperties() {
		//configure log4j
		URL log4jProperties = Thread.currentThread().getContextClassLoader().getResource(LOG4J_PROPERTIES_FILE_NAME);
		if (log4jProperties == null) {
			try {
				log4jProperties = new File(LOG4J_PROPERTIES_FILE_NAME).toURI().toURL();
			} catch (MalformedURLException mue) {
				/*ignore*/
			}
		}
		DOMConfigurator.configure(log4jProperties);
	}
	
	/**
	 * Loads the model properties. First an attempt for loading the file from
	 * the class path is made and if that fails an attempt for loading
	 * from the current folder is made.
	 */
	public static void loadModel() {
		String modelPropertiesFile = properties.getProperty("model.properties.file");
		URL modelProperties = Thread.currentThread().getContextClassLoader().getResource(modelPropertiesFile);
		if (modelProperties == null) {
			try {
				modelProperties = new File(modelPropertiesFile).toURI().toURL();
			} catch (MalformedURLException mue) {
				/*ignore*/
			}
		}
		if (modelProperties == null) {
			log.error("The file defined by property 'model.properties' was not properly set. The propgram will exit!");
			System.exit(1);
		}
		log.info("Model properties file present - OK");
		try {
			loadModelProperties(modelProperties);
		} catch (JAXBException je) {
			log.error("Parsing of xml file specified by property 'interface.method.mapping.file' failed." + 
				"Check schema! System will exit!", je);
			System.exit(1);
		}
		//load component name/class mapping
		initComponentFactory();
	}
	
	/**
	 * Loads the mappings between model properties from the XML file.
	 * 
	 * @param methodMappingFile The XML file.
	 * @throws JAXBException If any error occurs during the parsing of the file.
	 */
	private static void loadModelProperties(URL modelProperties) throws JAXBException {
		JAXBContext context = JAXBContext.newInstance("org.barad.architecture.analyzer.properties.bind");
		Unmarshaller unmarshaler = context.createUnmarshaller();
		@SuppressWarnings("unchecked")
		JAXBElement<Propertiez> jaxbObject = (JAXBElement<Propertiez>)unmarshaler.unmarshal(modelProperties);
		PropertiesHandler propertiesHandler = HandlerFactory.getArchPadPropertiesHandlerInstance();
		propertiesHandler.process(jaxbObject.getValue());
	}

	/**
	 * Initializes the {@link ComponentFactory} used for instantiating
	 * components of the current model. 
	 */
	private static void initComponentFactory() {
		HashMap<String, Class<?>> componentNameClassPairs = new HashMap<String, Class<?>>();
		for (ComponentDescriptor desc: componentProperties) {
			componentNameClassPairs.put(desc.getClassName(), null);
		}
		ComponentFactory.getInstance().setComponents(componentNameClassPairs);
	}
	
	/**
	 * Gets the components properties.
	 * 
	 * @return The components properties.
	 */
	public static List<ComponentDescriptor> getComponentProperties() {
		return componentProperties;
	}

	/**
	 * Sets the component properties.
	 * 
	 * @param components The component properties.
	 */
	public static void setComponentProperties(List<ComponentDescriptor> components) {
		Util.componentProperties = components;
	}

	/**
	 * Gets the current model name.
	 * 
	 * @return The model name.
	 */
	public static String getModelName() {
		return modelName;
	}

	/**
	 * Sets the current model name.
	 * 
	 * @param modelName The model name.
	 */
	public static void setModelName(String modelName) {
		Util.modelName = modelName;
	}
}
